package org.chipu.jnids.sancp;

import java.io.*;
import java.util.*;
import java.util.logging.Logger;
import org.chipu.jnids.display.TextConsole;
import org.chipu.shared.interfaces.Loggeable;
import org.chipu.shared.util.file.FileUtils;
import org.chipu.shared.util.singletons.*;

public class SancpManager implements Loggeable {
	/** SANCP log files greater than this limit (in bytes) will be splitted */
	private static final long filesizeLimit = 10 * 1024 * 1024;
	private static final long filesizeLineLimit = 120000;
	private static final String sancpUser = "sguil";
	private static final String sancpGroup = "nids";
	private Logger logger = Logger.getLogger(getClass().getName());
	private Process sancp;
	private final File sancpDataDir, sancpLogFile, sancpConfigFile;

	/** To do tests. It cannot start the process nor stop it */
	SancpManager(File sancpDataDir) {
		if (!sancpDataDir.isDirectory())
			throw new IllegalStateException("Sancp data directory is not a directory: " + sancpDataDir);
		this.sancpDataDir = sancpDataDir;
		sancpLogFile = null;
		sancpConfigFile = null;
	}

	public SancpManager(File sancpDataDir, String sancpLog, String sancpConfig, Logger logger) {
		this.logger = logger == null? Logger.getLogger(getClass().getSimpleName()): logger;
		if (!sancpDataDir.isDirectory())
			throw new IllegalStateException("Sancp data directory is not a directory: "
				+ sancpDataDir.getAbsolutePath());
		this.sancpDataDir = sancpDataDir;
		sancpLogFile = new File(sancpLog);
		if (!sancpLogFile.exists())
			if (sancpLogFile.getParentFile().mkdirs())
				throw new IllegalStateException("Could not create directory where sancp log file will be");
		if ((sancpLogFile.exists() && !sancpLogFile.canWrite())
			|| (!sancpLogFile.exists() && !sancpLogFile.getParentFile().canWrite()))
			throw new IllegalStateException("Sancp log file does not exists or is not writable: "
				+ sancpLogFile + " " + "(exists?" + sancpLogFile.exists() + ",canWrite?"
				+ sancpLogFile.canWrite() + ",canWriteDir?" + sancpLogFile.getParentFile().canWrite() + ")");
		sancpConfigFile = new File(sancpConfig);
		if (!sancpConfigFile.exists() || !sancpConfigFile.canRead())
			throw new IllegalStateException("Sancp config file is not readable: " + sancpConfigFile);
	}

	@Override
	public void setLogger(Logger logger) {
		if (logger != null)
			this.logger = logger;
	}

	/**
	 * Preprocess the SANCP files in the specified directory, so they are ready for the next step.<br>
	 * It splits the files if they are too big.
	 * @return If the operation was successful and there is enough data to create the profile
	 */
	public boolean preprocessProfileFiles() {
		if (!sancpDataDir.canWrite())
			throw new IllegalStateException("Sancp data directory is not writable: " + sancpDataDir);
		// preprocess
		for(File f : getSancpStatsLogFiles())
			if (f.length() > filesizeLimit) {
				File subDir = new File(sancpDataDir, f.getName() + "_tmp");
				if (subDir.exists()) {
					// TODO check that the tmp dir is complete (has the expected size)
				} else {
					subDir.mkdir();
					TextConsole.getInstance().startProgress("PreprocessSANCPFile", "File " + f);
					// split it
					String fileName = FileUtils.getBaseName(f);
					String fileExtension = FileUtils.getExtension(f);
					// using FileChannel does not respect the lines
					BufferedReader inSt = null;
					BufferedWriter outSt = null;
					int j = 0;
					try {
						// inCh = new FileInputStream(f).getChannel();
						inSt = new BufferedReader(new FileReader(f));
						// IMPROVE find the number of files (aprox), show percentage
						// int numberOfFiles = (int) f.length();
						for(long i = 0; i < f.length(); /* i += filesizeLimit, */j++) {
							File newFile = new File(subDir, fileName + "_"
								+ Utils.fillString(String.valueOf(j), '0', 3)
								+ ("".equals(fileExtension)? "": "." + fileExtension));
							// outCh = new FileOutputStream(newFile).getChannel();
							// inCh.transferTo(i, filesizeLimit, outCh);
							// outSt.close();
							outSt = new BufferedWriter(new FileWriter(newFile));
							for(int k = 0; k < filesizeLineLimit; k++) {
								String s = inSt.readLine();
								if (s == null)
									break;
								String ss = s + "\r\n";
								outSt.write(ss);
							}
							outSt.close();
							TextConsole.getInstance().doProgress("PreprocessSANCPFile");
							if (newFile.length() == 0) {
								if (!newFile.delete())
									newFile.deleteOnExit();
								break;
							}
							i += newFile.length();
						}
					} catch(Exception ex) {
						ex.printStackTrace();
					} finally {
						if (inSt != null)
							try {
								inSt.close();
							} catch(IOException ex) {
								ex.printStackTrace();
							}
						if (outSt != null)
							try {
								outSt.close();
							} catch(IOException ex) {
								ex.printStackTrace();
							}
					}
					// make a backup of the old file, so it wont be preprocessed again
					f.renameTo(new File(sancpDataDir, "_" + f.getName()));
					TextConsole.getInstance().endProgress("PreprocessSANCPFile", j + " files created");
				}
			}
		List<File> statsLogs = getSancpStatsLogFiles();
		LoggingUtils.getLogger().fine("Using files: " + statsLogs);

		boolean enough = isSancpDataEnough(statsLogs);
		if (!enough)
			LoggingUtils.getLogger().warning(
				"There are no enough files to fullfill the request in " + sancpDataDir.getAbsolutePath()
					+ ".\n\t" + statsLogs);
		return enough;
	}

	// ----- Utilities

	/**
	 * @return All the files that will be used to create the profile:
	 *         <ul>
	 *         <li>If <code>sancpDataDir</code>points to a file, return that file
	 *         <li>If <code>sancpDataDir</code>points to a dir, then returns alll the expanded files, if any,
	 *         and the normal stats
	 *         </ul>
	 */
	public List<File> getSancpStatsLogFiles() {
		if (sancpDataDir.isFile())
			return Arrays.asList(sancpDataDir);
		File[] subdirs = sancpDataDir.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return pathname != null && pathname.isDirectory() && pathname.getName().endsWith("_tmp");
			}
		});
		ArrayList<File> fs = new ArrayList<File>();
		fs.addAll(Arrays.asList(sancpDataDir.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return new File(dir, name).isFile() && name.startsWith("stats");
			}
		})));
		for(File dir : subdirs) {
			fs.remove(new File(sancpDataDir, dir.getName().substring(0, dir.getName().length() - 4)));
			fs.addAll(Arrays.asList(dir.listFiles(new FilenameFilter() {
				@Override
				public boolean accept(File dir, String name) {
					return name.startsWith("stats");
				}
			})));
		}
		return fs;
	}

	boolean isSancpDataEnough() {
		return isSancpDataEnough(getSancpStatsLogFiles());
	}

	static boolean isSancpDataEnough(List<File> statsLogs) {
		// IMPROVE use the size of the stats
		return statsLogs != null && !statsLogs.isEmpty();
	}

	static boolean isSancpDataEnough(File... statsLogs) {
		return isSancpDataEnough(Arrays.asList(statsLogs));
	}

	boolean isSancpRunning() {
		return LinuxUtils.isProcessRunning("sancp");
	}

	/**
	 * Exec:<br>
	 * <code>sudo sancp --human-readable -d /var/nsm/dali_eth0/sancp -i eth0 -u
	 * sguil -g nids -c /etc/nsm/sancp.conf > /var/log/sancp/sancp.log</code>
	 */
	boolean startSancp() {
		String command = "sudo sancp --human-readable -d " + sancpDataDir.getAbsolutePath() + " -i "
			+ NetworkUtils.getActiveInterfaceName() + " -u " + sancpUser + " -g " + sancpGroup + " -c "
			+ sancpConfigFile.getAbsolutePath() + " > " + sancpLogFile.getAbsolutePath();
		logger.finest("Starting sancp: " + command);
		sancp = LinuxUtils.runCommand(command);
		return true;
	}

	boolean stopSancp() {
		if (!isSancpRunning())
			return false;
		if (sancp != null) {
			sancp.destroy();
			return true;
		}
		return LinuxUtils.killProcess("sancp");
	}
}